package com.wzw.groovy.script;

import groovy.lang.*;
import groovy.util.ResourceException;
import org.apache.groovy.util.concurrent.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import org.apache.groovy.util.concurrent.concurrentlinkedhashmap.Weighers;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.WhileStatement;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
import org.codehaus.groovy.runtime.EncodingGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.syntax.Types;
import org.kohsuke.groovy.sandbox.GroovyInterceptor;
import org.kohsuke.groovy.sandbox.SandboxTransformer;

import javax.script.*;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Wangzhiwen
 */
public class JavaGroovyScript {

    public static ConcurrentLinkedHashMap<String, GroovyClassLoader> classLoaderCache =
            new ConcurrentLinkedHashMap.Builder<String, GroovyClassLoader>()
                    .maximumWeightedCapacity(512)
                    .weigher(Weighers.singleton())
                    .listener((key, value) -> {
                        try {
                            //  当缓存失效时，关闭classLoader, 这里关键点，只有发生full gc的时候才会回收类加载器
                            //  所以jvm一定要给元空间配置边界
                            value.close();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }).build();

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ScriptException, NoSuchAlgorithmException {
        groovyShellExecute();
        // groovyLoaderExecute();
        // jsr223Execute();
    }

    /**
     * 运行脚本方式一
     * GroovyShell
     */
    public static void groovyShellExecute() throws InstantiationException, IllegalAccessException {
        // 绑定参数
        Binding binding = new Binding(new ConcurrentHashMap<>(16));
        binding.setVariable("name", "GroovyShell execute success");
        binding.setVariable("org", "new Org()");
        // org.getName()
        // 构建shell对象
        GroovyShell groovyShell = new GroovyShell(binding);
        // 执行方式一
        groovyShell.evaluate("println(name)");

        // 执行方式二
        Script script = groovyShell.parse("println(name)");
        // script.run();
        // groovyShell.invokeMethod("run", "1");

        // Script script1 = groovyShell.parse("def a = 1;\n" +
        //         "def b = 2;\n" +
        //         "def c = a + b;\n" +
        //         "sum();\n" +
        //         "def sum() {\n" +
        //         "    print '123'\n" +
        //         "}");
        // Object run = script1.run();

        GroovyClassLoader classLoader = new GroovyClassLoader();
        Class<?> clazz = classLoader.parseClass("def a = 1;\n" +
                "def b = 2;\n" +
                "def c = a + b;\n" +
                "sum();\n" +
                "def sum() {\n" +
                "    println '123'\n" +
                "}");
        GroovyObject instance = (GroovyObject) clazz.newInstance();
        Script script2 = InvokerHelper.createScript(clazz, new Binding(new ConcurrentHashMap<>()));
        script2.run();
    }

    /**
     * 运行脚本方式二 GroovyClassLoader
     */
    public static void groovyLoaderExecute() throws InstantiationException, IllegalAccessException, NoSuchAlgorithmException {
        // 创建GroovyClassLoader
        GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
        String scriptTxt = "class Test1 {\n" +
                "\n" +
                "    String say(String name) {\n" +
                "        println name\n" +
                "        return name\n" +
                "    }\n" +
                "\n" +
                "}";

        // 解析脚本
        Class<?> testClass1 = groovyClassLoader.parseClass(scriptTxt);
        Class<?> testClass2 = groovyClassLoader.parseClass(scriptTxt);

        String name = "Script_" + EncodingGroovyMethods.md5(scriptTxt) + ".groovy";
        PrivilegedAction<GroovyCodeSource> action = () -> new GroovyCodeSource(scriptTxt, name, "/groovy/script");
        GroovyCodeSource gcs = action.run();

        Class<?> aClass = groovyClassLoader.parseClass(gcs);
        Class<?> bClass = groovyClassLoader.parseClass(gcs);

        PrivilegedAction<GroovyCodeSource> action2 = () -> new GroovyCodeSource(scriptTxt, name, "/groovy/script");
        GroovyCodeSource gcs2 = action.run();
        Class<?> cClass = groovyClassLoader.parseClass(gcs2);
        Class<?> dClass = groovyClassLoader.parseClass(gcs2, true);

        // 强制转换为GroovyObject后执行方法
        GroovyObject object = (GroovyObject) testClass1.newInstance();
        object.invokeMethod("say", "GroovyClassLoader execute success");

        // 转换成Script后执行run方法
        Class<?> eClass = groovyClassLoader.parseClass("");
        Binding binding = new Binding(new ConcurrentHashMap<>(2));
        binding.setVariable("name", "Script binding");
        Script script = InvokerHelper.createScript(eClass, binding);
        Object run = script.run();
        System.out.println(run);

    }

    public static void groovyScriptEngineExecute() throws IOException, groovy.util.ScriptException, ResourceException {
        // GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(new String[]{"classpath://111."});
        // String r1 = groovyScriptEngine.run("脚本名称", "参数");
        // Script script = groovyScriptEngine.createScript("脚本名名称", "参数");
        // Object r2 = script.run();
    }

    /**
     * 运行脚本方式三：使用Java规范的API
     */
    public static void jsr223Execute() throws ScriptException {
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("groovy");
        Bindings bindings = scriptEngine.createBindings();
        bindings.put("name", "jsr223 execute success");
        scriptEngine.eval("println(name)", bindings);
        CompiledScript compile = ((Compilable) scriptEngine).compile("println(name)");
        compile.eval(bindings);
        GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine;
        // 定制GroovyClassLoader
        // groovyScriptEngine.setClassLoader();
    }

    public static void groovyScriptSecurity() {
        // 创建SecureASTCustomizer
        final SecureASTCustomizer secure = new SecureASTCustomizer();
        // 禁止使用闭包
        secure.setClosuresAllowed(true);
        // 禁用关键字 while goto
        List<Integer> disallowedTokenslist = new ArrayList<>();
        disallowedTokenslist.add(Types.KEYWORD_WHILE);
        disallowedTokenslist.add(Types.KEYWORD_GOTO);
        secure.setDisallowedTokens(disallowedTokenslist);
        ImportCustomizer importCustomizer = new ImportCustomizer();
        // 设置直接导入检查
        secure.setIndirectImportCheckEnabled(true);
        // 禁用导入类
        List<String> list = new ArrayList<>();
        list.add("com.alibaba.fastjson.JSONObject");
        secure.setDisallowedImports(list);
        importCustomizer.addImport("1", "ccc");
        // 1.tun()
        // statement 黑名单，不能使用while循环块
        List<Class<? extends Statement>> statementBlacklist = new ArrayList<>();
        statementBlacklist.add(WhileStatement.class);
        secure.setDisallowedStatements(statementBlacklist);
        // 创建编译器配置CompilerConfiguration
        final CompilerConfiguration config = new CompilerConfiguration();
        // 添加客制化
        config.addCompilationCustomizers(secure);
        // 传入配置项
        GroovyShell groovyShell = new GroovyShell(config);
        // org mans
        GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getClass().getClassLoader(), config);
        // 加载这个脚本的时候  我们就需要限定一些配置
        groovyClassLoader.parseClass("");
    }

    public static void sandboxTransformer() {
        // 沙箱配置类
        SandboxTransformer sandboxTransformer = new SandboxTransformer();
        CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
        // 添加到编译器配置类中
        compilerConfiguration.addCompilationCustomizers(sandboxTransformer);
        // 注册拦截器（线程绑定）
        new NoSystemExitSandbox().register();
        new NoRunTimeSandbox().register();
        new CusSandbox().register();
        // 运行脚本
        GroovyShell groovyShell = new GroovyShell(compilerConfiguration);

        GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().getClass().getClassLoader(), compilerConfiguration);
        Class<?> aClass = classLoader.parseClass("1111");
        Map<String, Script> map = new ConcurrentHashMap<>(2);
    }

    /**
     * System.exit(0); 拦截
     */
    public static class NoSystemExitSandbox extends GroovyInterceptor {
        @Override
        public Object onStaticCall(Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
            if (receiver == System.class && Objects.equals(method, "exit")) {
                throw new SecurityException("No call on System.exit() please");
            }
            return super.onStaticCall(invoker, receiver, method, args);
        }
    }

    /**
     * Runtime; 拦截
     */
    public static class NoRunTimeSandbox extends GroovyInterceptor {
        @Override
        public Object onStaticCall(Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
            if (receiver == Runtime.class) {
                throw new SecurityException("No call on RunTime please");
            }
            return super.onStaticCall(invoker, receiver, method, args);
        }
    }

    /**
     * 自定义拦截
     */
    public static class CusSandbox extends GroovyInterceptor {
        @Override
        public Object onMethodCall(Invoker invoker, Object receiver, String method, Object... args) throws Throwable {
            return super.onMethodCall(invoker, receiver, method, args);
        }

        @Override
        public Object onStaticCall(Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
            return super.onStaticCall(invoker, receiver, method, args);
        }

        @Override
        public Object onNewInstance(Invoker invoker, Class receiver, Object... args) throws Throwable {
            return super.onNewInstance(invoker, receiver, args);
        }

        @Override
        public Object onSuperCall(Invoker invoker, Class senderType, Object receiver, String method, Object... args) throws Throwable {
            return super.onSuperCall(invoker, senderType, receiver, method, args);
        }

        @Override
        public void onSuperConstructor(Invoker invoker, Class receiver, Object... args) throws Throwable {
            super.onSuperConstructor(invoker, receiver, args);
        }

        @Override
        public Object onGetProperty(Invoker invoker, Object receiver, String property) throws Throwable {
            return super.onGetProperty(invoker, receiver, property);
        }

        @Override
        public Object onSetProperty(Invoker invoker, Object receiver, String property, Object value) throws Throwable {
            return super.onSetProperty(invoker, receiver, property, value);
        }

        @Override
        public Object onGetAttribute(Invoker invoker, Object receiver, String attribute) throws Throwable {
            return super.onGetAttribute(invoker, receiver, attribute);
        }

        @Override
        public Object onSetAttribute(Invoker invoker, Object receiver, String attribute, Object value) throws Throwable {
            return super.onSetAttribute(invoker, receiver, attribute, value);
        }

        @Override
        public Object onGetArray(Invoker invoker, Object receiver, Object index) throws Throwable {
            return super.onGetArray(invoker, receiver, index);
        }

        @Override
        public Object onSetArray(Invoker invoker, Object receiver, Object index, Object value) throws Throwable {
            return super.onSetArray(invoker, receiver, index, value);
        }
    }
}
